/*
 * Copyright (C) 2005 - 2013 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008 - 2013 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010 - 2013 ProjectSkyfire <http://www.projectskyfire.org/>
 *
 * Copyright (C) 2011 - 2013 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef ARKCORECORE_ITEM_H
#define ARKCORECORE_ITEM_H

#include "Common.h"
#include "Object.h"
#include "LootMgr.h"
#include "ItemPrototype.h"
#include "DatabaseEnv.h"

struct SpellEntry;
class Bag;
class Unit;

struct ItemSetEffect
{
    uint32 setid;
    uint32 item_count;
    SpellEntry const *spells[8];
};

enum InventoryChangeFailure
{
    EQUIP_ERR_OK                                 = 0,
    EQUIP_ERR_CANT_EQUIP_LEVEL_I                 = 1,
    EQUIP_ERR_CANT_EQUIP_SKILL                   = 2,
    EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT             = 3,
    EQUIP_ERR_BAG_FULL                           = 4,
    EQUIP_ERR_NONEMPTY_BAG_OVER_OTHER_BAG        = 5,
    EQUIP_ERR_CANT_TRADE_EQUIP_BAGS              = 6,
    EQUIP_ERR_ONLY_AMMO_CAN_GO_HERE              = 7,
    EQUIP_ERR_NO_REQUIRED_PROFICIENCY            = 8,
    EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE        = 9,
    EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM        = 10,
    EQUIP_ERR_YOU_CAN_NEVER_USE_THAT_ITEM2       = 11,
    EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE2       = 12,
    EQUIP_ERR_CANT_EQUIP_WITH_TWOHANDED          = 13,
    EQUIP_ERR_CANT_DUAL_WIELD                    = 14,
    EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG            = 15,
    EQUIP_ERR_ITEM_DOESNT_GO_INTO_BAG2           = 16,
    EQUIP_ERR_CANT_CARRY_MORE_OF_THIS            = 17,
    EQUIP_ERR_NO_EQUIPMENT_SLOT_AVAILABLE3       = 18,
    EQUIP_ERR_ITEM_CANT_STACK                    = 19,
    EQUIP_ERR_ITEM_CANT_BE_EQUIPPED              = 20,
    EQUIP_ERR_ITEMS_CANT_BE_SWAPPED              = 21,
    EQUIP_ERR_SLOT_IS_EMPTY                      = 22,
    EQUIP_ERR_ITEM_NOT_FOUND                     = 23,
    EQUIP_ERR_CANT_DROP_SOULBOUND                = 24,
    EQUIP_ERR_OUT_OF_RANGE                       = 25,
    EQUIP_ERR_TRIED_TO_SPLIT_MORE_THAN_COUNT     = 26,
    EQUIP_ERR_COULDNT_SPLIT_ITEMS                = 27,
    EQUIP_ERR_MISSING_REAGENT                    = 28,
    EQUIP_ERR_NOT_ENOUGH_MONEY                   = 29,
    EQUIP_ERR_NOT_A_BAG                          = 30,
    EQUIP_ERR_CAN_ONLY_DO_WITH_EMPTY_BAGS        = 31,
    EQUIP_ERR_DONT_OWN_THAT_ITEM                 = 32,
    EQUIP_ERR_CAN_EQUIP_ONLY1_QUIVER             = 33,
    EQUIP_ERR_MUST_PURCHASE_THAT_BAG_SLOT        = 34,
    EQUIP_ERR_TOO_FAR_AWAY_FROM_BANK             = 35,
    EQUIP_ERR_ITEM_LOCKED                        = 36,
    EQUIP_ERR_YOU_ARE_STUNNED                    = 37,
    EQUIP_ERR_YOU_ARE_DEAD                       = 38,
    EQUIP_ERR_CANT_DO_RIGHT_NOW                  = 39,
    EQUIP_ERR_INT_BAG_ERROR                      = 40,
    EQUIP_ERR_CAN_EQUIP_ONLY1_BOLT               = 41,
    EQUIP_ERR_CAN_EQUIP_ONLY1_AMMOPOUCH          = 42,
    EQUIP_ERR_STACKABLE_CANT_BE_WRAPPED          = 43,
    EQUIP_ERR_EQUIPPED_CANT_BE_WRAPPED           = 44,
    EQUIP_ERR_WRAPPED_CANT_BE_WRAPPED            = 45,
    EQUIP_ERR_BOUND_CANT_BE_WRAPPED              = 46,
    EQUIP_ERR_UNIQUE_CANT_BE_WRAPPED             = 47,
    EQUIP_ERR_BAGS_CANT_BE_WRAPPED               = 48,
    EQUIP_ERR_ALREADY_LOOTED                     = 49,
    EQUIP_ERR_INVENTORY_FULL                     = 50,
    EQUIP_ERR_BANK_FULL                          = 51,
    EQUIP_ERR_ITEM_IS_CURRENTLY_SOLD_OUT         = 52,
    EQUIP_ERR_BAG_FULL3                          = 53,
    EQUIP_ERR_ITEM_NOT_FOUND2                    = 54,
    EQUIP_ERR_ITEM_CANT_STACK2                   = 55,
    EQUIP_ERR_BAG_FULL4                          = 56,
    EQUIP_ERR_ITEM_SOLD_OUT                      = 57,
    EQUIP_ERR_OBJECT_IS_BUSY                     = 58,
    EQUIP_ERR_NONE                               = 59,
    EQUIP_ERR_NOT_IN_COMBAT                      = 60,
    EQUIP_ERR_NOT_WHILE_DISARMED                 = 61,
    EQUIP_ERR_BAG_FULL6                          = 62,
    EQUIP_ERR_CANT_EQUIP_RANK                    = 63,
    EQUIP_ERR_CANT_EQUIP_REPUTATION              = 64,
    EQUIP_ERR_TOO_MANY_SPECIAL_BAGS              = 65,
    EQUIP_ERR_LOOT_CANT_LOOT_THAT_NOW            = 66,
    EQUIP_ERR_ITEM_UNIQUE_EQUIPABLE              = 67,
    EQUIP_ERR_VENDOR_MISSING_TURNINS             = 68,
    EQUIP_ERR_NOT_ENOUGH_HONOR_POINTS            = 69,
    EQUIP_ERR_NOT_ENOUGH_ARENA_POINTS            = 70,
    EQUIP_ERR_ITEM_MAX_COUNT_SOCKETED            = 71,
    EQUIP_ERR_MAIL_BOUND_ITEM                    = 72,
    EQUIP_ERR_NO_SPLIT_WHILE_PROSPECTING         = 73,
    EQUIP_ERR_ITEM_MAX_COUNT_EQUIPPED_SOCKETED   = 75,
    EQUIP_ERR_ITEM_UNIQUE_EQUIPPABLE_SOCKETED    = 76,
    EQUIP_ERR_TOO_MUCH_GOLD                      = 77,
    EQUIP_ERR_NOT_DURING_ARENA_MATCH             = 78,
    EQUIP_ERR_CANNOT_TRADE_THAT                  = 79,
    EQUIP_ERR_PERSONAL_ARENA_RATING_TOO_LOW      = 80,
    EQUIP_ERR_EVENT_AUTOEQUIP_BIND_CONFIRM       = 81,
    EQUIP_ERR_ARTEFACTS_ONLY_FOR_OWN_CHARACTERS  = 82,
    EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_COUNT_EXCEEDED     = 84,
    EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_SOCKETED_EXCEEDED  = 85,
    EQUIP_ERR_SCALING_STAT_ITEM_LEVEL_EXCEEDED           = 86,
    EQUIP_ERR_PURCHASE_LEVEL_TOO_LOW                     = 87,
    EQUIP_ERR_CANT_EQUIP_NEED_TALENT                     = 88,
    EQUIP_ERR_ITEM_MAX_LIMIT_CATEGORY_EQUIPPED_EXCEEDED  = 89,
    EQUIP_ERR_SHAPESHIFT_FORM_CANNOT_EQUIP                 = 90, // Cannot equip item in this form
    EQUIP_ERR_ITEM_INVENTORY_FULL_SATCHEL                  = 91, // Your inventory is full. Your satchel has been delivered to your mailbox.
    EQUIP_ERR_SCALING_STAT_ITEM_LEVEL_TOO_LOW              = 92, // Your level is too low to use that item
    EQUIP_ERR_CANT_BUY_QUANTITY                            = 93 // You can't buy the specified quantity of that item.	
};


enum BuyFailure
{
    BUY_ERR_CANT_FIND_ITEM = 0, BUY_ERR_ITEM_ALREADY_SOLD = 1, BUY_ERR_NOT_ENOUGHT_MONEY = 2, BUY_ERR_SELLER_DONT_LIKE_YOU = 4, BUY_ERR_DISTANCE_TOO_FAR = 5, BUY_ERR_ITEM_SOLD_OUT = 7, BUY_ERR_CANT_CARRY_MORE = 8, BUY_ERR_RANK_REQUIRE = 11, BUY_ERR_REPUTATION_REQUIRE = 12
};

enum SellFailure
{
    SELL_ERR_CANT_FIND_ITEM 		= 1, 
	SELL_ERR_CANT_SELL_ITEM 		= 2,    // merchant doesn't like that item
    SELL_ERR_CANT_FIND_VENDOR		= 3,    // merchant doesn't like you
    SELL_ERR_YOU_DONT_OWN_THAT_ITEM = 4,    // you don't own that item
    SELL_ERR_UNK 					= 5,    // nothing appears...
    SELL_ERR_ONLY_EMPTY_BAG 		= 6
// can only do with empty bags
};

enum FakeResult
{
    FAKE_ERR_CANT_FIND_OWNER,
    FAKE_ERR_CANT_FIND_ITEM,
    FAKE_ERR_WRONG_QUALITY,
    FAKE_ERR_DIFF_INVENTORYTYPE,
    FAKE_ERR_DIFF_CLASS,
    FAKE_ERR_DIFF_RACE,
    FAKE_ERR_DIFF_SUBCLASS,
    FAKE_ERR_INVALID_CLASS,
    FAKE_ERR_OK
};

// -1 from client enchantment slot number
enum EnchantmentSlot
{
    PERM_ENCHANTMENT_SLOT = 0,          // ITEM_FIELD_ENCHANTMENT_1_1
    TEMP_ENCHANTMENT_SLOT = 1,          // ITEM_FIELD_ENCHANTMENT_2_1
    SOCK_ENCHANTMENT_SLOT = 2,          // ITEM_FIELD_ENCHANTMENT_3_1
    SOCK_ENCHANTMENT_SLOT_2 = 3,          // ITEM_FIELD_ENCHANTMENT_4_1
    SOCK_ENCHANTMENT_SLOT_3 = 4,          // ITEM_FIELD_ENCHANTMENT_5_1
    BONUS_ENCHANTMENT_SLOT = 5,          // ITEM_FIELD_ENCHANTMENT_6_1
    PRISMATIC_ENCHANTMENT_SLOT = 6,          // ITEM_FIELD_ENCHANTMENT_7_1  added at apply special permanent enchantment
    MAX_INSPECTED_ENCHANTMENT_SLOT = 7,

    ENCHANTMENT_SLOT_7 = 7,          // ITEM_FIELD_ENCHANTMENT_8_1  unknown
    REFORGE_ENCHANTMENT_SLOT = 8,          // ITEM_FIELD_ENCHANTMENT_9_1  used with Reforge

    // NOTE: the following definitions are unconfirmed and the random property system is not working atm

    PROP_ENCHANTMENT_SLOT_0 = 9,          // ITEM_FIELD_ENCHANTMENT_10_1 used with RandomSuffix
    PROP_ENCHANTMENT_SLOT_1 = 10,          // ITEM_FIELD_ENCHANTMENT_11_1 used with RandomSuffix
    PROP_ENCHANTMENT_SLOT_2 = 11,          // ITEM_FIELD_ENCHANTMENT_12_1 used with RandomSuffix and RandomProperty
    PROP_ENCHANTMENT_SLOT_3 = 12,          // ITEM_FIELD_ENCHANTMENT_13_1 used with RandomProperty
    PROP_ENCHANTMENT_SLOT_4 = 13,          // ITEM_FIELD_ENCHANTMENT_14_1 used with RandomProperty
    MAX_ENCHANTMENT_SLOT = 14
};

#define MAX_VISIBLE_ITEM_OFFSET       2                     // 2 fields per visible item (entry+enchantment)
#define MAX_GEM_SOCKETS               MAX_ITEM_PROTO_SOCKETS// (BONUS_ENCHANTMENT_SLOT-SOCK_ENCHANTMENT_SLOT) and item proto size, equal value expected
enum EnchantmentOffset
{
    ENCHANTMENT_ID_OFFSET = 0, ENCHANTMENT_DURATION_OFFSET = 1, ENCHANTMENT_CHARGES_OFFSET = 2
// now here not only charges, but something new in wotlk
};

#define MAX_ENCHANTMENT_OFFSET    3

enum EnchantmentSlotMask
{
    ENCHANTMENT_CAN_SOULBOUND = 0x01, ENCHANTMENT_UNK1 = 0x02, ENCHANTMENT_UNK2 = 0x04, ENCHANTMENT_UNK3 = 0x08
};

enum ItemUpdateState
{
    ITEM_UNCHANGED = 0, ITEM_CHANGED = 1, ITEM_NEW = 2, ITEM_REMOVED = 3
};

enum ItemRequiredTargetType
{
    ITEM_TARGET_TYPE_CREATURE = 1, ITEM_TARGET_TYPE_DEAD = 2
};

#define MAX_ITEM_REQ_TARGET_TYPE 2

#define MAX_ITEM_SPELLS 5

struct ItemRequiredTarget
{
    ItemRequiredTarget (ItemRequiredTargetType uiType, uint32 uiTargetEntry) :
            m_uiType(uiType), m_uiTargetEntry(uiTargetEntry)
    {
    }
    ItemRequiredTargetType m_uiType;
    uint32 m_uiTargetEntry;

    // helpers
    bool IsFitToRequirements (Unit* pUnitTarget) const;
};

bool ItemCanGoIntoBag (ItemPrototype const *proto, ItemPrototype const *pBagProto);

class Item: public Object
{
public:
    static Item* CreateItem (uint32 item, uint32 count, Player const* player = NULL);
    Item* CloneItem (uint32 count, Player const* player = NULL) const;

    Item ();

    virtual bool Create (uint32 guidlow, uint32 itemid, Player const* owner);

    ItemPrototype const* GetProto () const;

    uint64 const& GetOwnerGUID () const
    {
        return GetUInt64Value(ITEM_FIELD_OWNER);
    }
    void SetOwnerGUID (uint64 guid)
    {
        SetUInt64Value(ITEM_FIELD_OWNER, guid);
    }
    Player* GetOwner () const;

    void SetBinding (bool val)
    {
        ApplyModFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_SOULBOUND, val);
    }
    bool IsSoulBound () const
    {
        return HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_SOULBOUND);
    }
    bool IsBoundAccountWide () const
    {
        return (GetProto()->Flags & ITEM_PROTO_FLAG_BIND_TO_ACCOUNT) != 0;
    }
    bool IsBindedNotWith (Player const* player) const;
    bool IsBoundByEnchant () const;
    virtual void SaveToDB (SQLTransaction& trans);
    virtual bool LoadFromDB (uint32 guid, uint64 owner_guid, Field* fields, uint32 entry);
    virtual void DeleteFromDB (SQLTransaction& trans);
    void DeleteFromInventoryDB (SQLTransaction& trans);
    void SaveRefundDataToDB ();
    void DeleteRefundDataFromDB ();

    Bag* ToBag ()
    {
        if (IsBag())
            return reinterpret_cast<Bag*>(this);
        else
            return NULL;
    }
    const Bag* ToBag () const
    {
        if (IsBag())
            return reinterpret_cast<const Bag*>(this);
        else
            return NULL;
    }

    bool IsLocked () const
    {
        return !HasFlag(ITEM_FIELD_FLAGS, ITEM_FLAG_UNLOCKED);
    }
    bool IsBag () const
    {
        return GetProto()->InventoryType == INVTYPE_BAG;
    }
    bool IsBroken () const
    {
        return GetUInt32Value(ITEM_FIELD_MAXDURABILITY) > 0 && GetUInt32Value(ITEM_FIELD_DURABILITY) == 0;
    }
    bool CanBeTraded (bool mail = false, bool trade = false) const;
    void SetInTrade (bool b = true)
    {
        mb_in_trade = b;
    }
    bool IsInTrade () const
    {
        return mb_in_trade;
    }

    bool HasEnchantRequiredSkill (const Player *pPlayer) const;
    uint32 GetEnchantRequiredLevel () const;

    bool IsFitToSpellRequirements (SpellEntry const* spellInfo) const;
    bool IsTargetValidForItemUse (Unit* pUnitTarget);
    bool IsLimitedToAnotherMapOrZone (uint32 cur_mapId, uint32 cur_zoneId) const;
    bool GemsFitSockets () const;

    uint32 GetCount () const
    {
        return GetUInt32Value(ITEM_FIELD_STACK_COUNT);
    }
    void SetCount (uint32 value)
    {
        SetUInt32Value(ITEM_FIELD_STACK_COUNT, value);
    }
    uint32 GetMaxStackCount () const
    {
        return GetProto()->GetMaxStackSize();
    }
    uint8 GetGemCountWithID (uint32 GemID) const;
    uint8 GetGemCountWithLimitCategory (uint32 limitCategory) const;
    uint8 CanBeMergedPartlyWith (ItemPrototype const* proto) const;

    uint8 GetSlot () const
    {
        return m_slot;
    }
    Bag *GetContainer ()
    {
        return m_container;
    }
    uint8 GetBagSlot () const;
    void SetSlot (uint8 slot)
    {
        m_slot = slot;
    }
    uint16 GetPos () const
    {
        return uint16(GetBagSlot()) << 8 | GetSlot();
    }
    void SetContainer (Bag *container)
    {
        m_container = container;
    }

    bool IsInBag () const
    {
        return m_container != NULL;
    }
    bool IsEquipped () const;

    uint32 GetSkill ();
    uint32 GetSpell ();

    // RandomPropertyId (signed but stored as unsigned)
    int32 GetItemRandomPropertyId () const
    {
        return GetInt32Value(ITEM_FIELD_RANDOM_PROPERTIES_ID);
    }
    uint32 GetItemSuffixFactor () const
    {
        return GetUInt32Value(ITEM_FIELD_PROPERTY_SEED);
    }
    void SetItemRandomProperties (int32 randomPropId);
    void UpdateItemSuffixFactor ();
    static int32 GenerateItemRandomPropertyId (uint32 item_id);
    void SetEnchantment (EnchantmentSlot slot, uint32 id, uint32 duration, uint32 charges);
    void SetEnchantmentDuration (EnchantmentSlot slot, uint32 duration, Player* owner);
    void SetEnchantmentCharges (EnchantmentSlot slot, uint32 charges);
    void ClearEnchantment (EnchantmentSlot slot);
    uint32 GetEnchantmentId (EnchantmentSlot slot) const
    {
        return GetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot * MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_ID_OFFSET);
    }
    uint32 GetEnchantmentDuration (EnchantmentSlot slot) const
    {
        return GetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot * MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_DURATION_OFFSET);
    }
    uint32 GetEnchantmentCharges (EnchantmentSlot slot) const
    {
        return GetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1 + slot * MAX_ENCHANTMENT_OFFSET + ENCHANTMENT_CHARGES_OFFSET);
    }

    std::string const& GetText () const
    {
        return m_text;
    }
    void SetText (std::string const& text)
    {
        m_text = text;
    }

    void SendTimeUpdate (Player* owner);
    void UpdateDuration (Player* owner, uint32 diff);

    // spell charges (signed but stored as unsigned)
    int32 GetSpellCharges (uint8 index/*0..5*/= 0) const
    {
        return GetInt32Value(ITEM_FIELD_SPELL_CHARGES + index);
    }
    void SetSpellCharges (uint8 index/*0..5*/, int32 value)
    {
        SetInt32Value(ITEM_FIELD_SPELL_CHARGES + index, value);
    }

    Loot loot;
    bool m_lootGenerated;

    // Update States
    ItemUpdateState GetState () const
    {
        return uState;
    }
    void SetState (ItemUpdateState state, Player *forplayer = NULL);
    void AddToUpdateQueueOf (Player *player);
    void RemoveFromUpdateQueueOf (Player *player);
    bool IsInUpdateQueue () const
    {
        return uQueuePos != -1;
    }
    uint16 GetQueuePos () const
    {
        return uQueuePos;
    }
    void FSetState (ItemUpdateState state)          // forced
    {
        uState = state;
    }

    bool hasQuest (uint32 quest_id) const
    {
        return GetProto()->StartQuest == quest_id;
    }
    bool hasInvolvedQuest (uint32 /*quest_id*/) const
    {
        return false;
    }
    bool IsPotion () const
    {
        return GetProto()->IsPotion();
    }
    bool IsWeaponVellum () const
    {
        return GetProto()->IsWeaponVellum();
    }
    bool IsArmorVellum () const
    {
        return GetProto()->IsArmorVellum();
    }
    bool IsConjuredConsumable () const
    {
        return GetProto()->IsConjuredConsumable();
    }

    // Item Refund system
    void SetNotRefundable (Player *owner, bool changestate = true);
    void SetRefundRecipient (uint32 pGuidLow)
    {
        m_refundRecipient = pGuidLow;
    }
    void SetPaidMoney (uint32 money)
    {
        m_paidMoney = money;
    }
    void SetPaidExtendedCost (uint32 iece)
    {
        m_paidExtendedCost = iece;
    }
    uint32 GetRefundRecipient ()
    {
        return m_refundRecipient;
    }
    uint32 GetPaidMoney ()
    {
        return m_paidMoney;
    }
    uint32 GetPaidExtendedCost ()
    {
        return m_paidExtendedCost;
    }

    void UpdatePlayedTime (Player *owner);
    uint32 GetPlayedTime ();
    bool IsRefundExpired ();

    // Soulbound trade system
    void SetSoulboundTradeable (AllowedLooterSet* allowedLooters, Player* currentOwner, bool apply);
    bool CheckSoulboundTradeExpire ();

    void BuildUpdate (UpdateDataMapType&);

    uint32 GetScriptId() const { return GetProto()->ScriptId; }
    FakeResult SetFakeDisplay(uint32 iEntry);
    uint32 GetFakeDisplayEntry() { return m_fakeDisplayEntry; }
    void RemoveFakeDisplay();
private:
    std::string m_text;
    uint8 m_slot;
    Bag *m_container;
    ItemUpdateState uState;
    int16 uQueuePos;
    bool mb_in_trade;          // true if item is currently in trade-window
    time_t m_lastPlayedTimeUpdate;
    uint32 m_refundRecipient;
    uint32 m_paidMoney;
    uint32 m_paidExtendedCost;
    AllowedLooterSet allowedGUIDs;
    uint32 m_fakeDisplayEntry;	
};
#endif
